任务 2:数据预处理和知识库构建 — 实现思路整理

本文档整理「数据预处理和知识库构建」的实现思路,以 mermaid 图与测试结果辅助说明。数据源为 results/ 下 15 个 .md 文件(lec01~lec05、hw01~hw05、hw01_sol~hw05_sol),暂不实现 PDF 转文本。

任务2 完成内容概览(任务与实现对应见 §7、§8):


1. 整体流程

flowchart LR subgraph input [数据源] MD[results 下 15 个 md] CM[content_map.json] DJ[data.json 元数据] end subgraph step2_2 [2.2 文档索引] Scan[扫描 md 文件名] Parse[解析 doc_id 与 doc_type] Enrich[可选补全 title 与 description] Index[config/doc_index.json] end subgraph step2_1 [2.1 预处理] Load[md_loader 读入与清洗] Section[章节结构识别] end subgraph step2_3 [2.3 切片与向量化] Split[按章节与长度切片] Meta[元数据标注] Chroma[写入 Chroma] end subgraph step2_4 [2.4 知识图谱] subgraph step2_4_1 [2.4.1 基础结构] Topics[Topic 与 Exercise] Prereq[PREREQUISITE 链] Covers[COVERS 关系] end subgraph step2_4_2 [2.4.2 概念与细粒度] Concept[Concept 节点] Teaches[TEACHES] Practices[PRACTICES] Depends[DEPENDS_ON] end subgraph step2_4_3 [2.4.3 验证与优化] Valid[先修/覆盖验证] Idx[索引优化] end Neo4j[导入 Neo4j] end MD --> Scan CM --> Enrich DJ --> Enrich Scan --> Parse --> Enrich --> Index Index --> Load MD --> Load Load --> Section Section --> Split Split --> Meta Meta --> Chroma Index --> Topics Topics --> Prereq Topics --> Covers Prereq --> Concept Covers --> Concept Concept --> Teaches Concept --> Practices Concept --> Depends Teaches --> Valid Practices --> Valid Depends --> Idx Valid --> Neo4j Idx --> Neo4j

1.5 常见疑问


2. 文档索引与分类(2.2)

对应 tasks 2.2.1~2.2.4。

2.2.1 思路

2.2.2 文件名与类型对应

flowchart TB subgraph files [文件名解析] F1["hash_..._lec01.md"] F2["hash_..._hw01.md"] F3["hash_..._hw01_sol.md"] end subgraph types [doc_type 与 doc_id] T1[lecture / lec01] T2[homework / hw01] T3[solution / hw01_sol] end F1 --> T1 F2 --> T2 F3 --> T3

2.2.3 content_map 中 hash→data.json 路径的映射示意

results/ 下 .md 文件名为 {hash}_MITRES_6_007S11_{doc_id}.md,其中 hash 为 32 位无连字符的 UUID。补全 title/description 时需用 data/res.6-007-spring-2011/content_map.json 查表,其 key 为带连字符的 UUID(8-4-4-4-12),value 为 data.json 的相对路径。映射关系如下:

flowchart LR subgraph results [results 文件名] H["hash 32 位无连字符\n例: e6c75426e220f1a4..."] end subgraph convert [转换] U["UUID 8-4-4-4-12\n例: e6c75426-e220-f1a4-06b0-b9be1f55bbc1"] end subgraph cm [content_map.json] K["key: 上列 UUID"] V["value: /resources/mitres_6_007s11_lec01/data.json"] end subgraph data [实际文件] DJ["data_root + value 去掉首斜杠\n→ .../resources/.../data.json"] end H --> U U --> K K --> V V --> DJ

即:hash_to_uuid(hash)content_map[uuid_key] 得到路径字符串 → data_root / path.lstrip("/") 得到 data.json 的完整路径,再读取其中的 titledescription

2.2.4 索引条目结构(示例)

索引中每条包含:file_pathfile_namehashdoc_iddoc_typelecture_index、可选 title/descriptionrelated_hw/related_sol/related_lec。示例(来自 config/doc_index.json):

字段 示例值
doc_id lec04, hw04, hw04_sol
doc_type lecture, homework, solution
title Lecture 4, Convolution / Problem Set 4 / Problem Set 4 Solutions
related_lec / related_hw / related_sol 按讲次与作业/答案对应

2.2.5 测试结果

见 §6 测试汇总。


3. 文本清洗与章节识别(2.1)

对应 tasks 2.1.2~2.1.3。

3.1 思路(2.1.2 / 2.1.3)

3.2 章节到切片的衔接

flowchart LR Raw[原始 md] Clean[clean_md_text] Head[parse_md_headings] Sections[Section 列表] Raw --> Clean Clean --> Head Head --> Sections Sections -->|边界与标题| Splitter[splitter 按节切]

3.3 测试结果

见 §6 测试汇总。


4. 文档切片与向量化(2.3)

对应 tasks 2.3.1~2.3.3。

4.1 切片策略(2.3.1)

单篇文档的“章节→切片”边界示意: 以某篇讲义(如 lec01)为例,parse_md_headings 得到若干 Section(level、title、start_offset、end_offset)。slice_document 先按 Section 切:若某节长度 ≤ max_section_chars(默认 800),则该节对应 1 个 chunk;若超过,则对该节内容再按 _split_by_size(chunk_size=600、overlap=75)拆成多个 chunk。示意如下:

flowchart TB Doc[单篇 md] Sections[Section 列表] S1["Section 1 短"] S2["Section 2 长"] C1[chunk 0] C2[chunk 1] C3[chunk 2] Doc --> Sections Sections --> S1 Sections --> S2 S1 --> C1 S2 --> C2 S2 --> C3
章节(section_title) 该节字符数 对应切片(chunk_index)
1 Introduction 约 400 chunk 0
Suggested Reading 约 200 chunk 1
某长节 约 1500 chunk 2, 3(按长度再分)

同一文档的 total_chunks 为该文档的 chunk 总数,每个 chunk 的 metadata 中带 section_titlechunk_index

4.2 切片元数据(2.3.2,与 design 对齐)

每个切片至少带:source_filedoc_typedoc_idsection_titlechunk_indextotal_chunkscontent_type,可选 title。Chroma 只接受标量 metadata,由 chunk_metadata_for_chroma() 做扁平化。

4.4 文档向量的生成方式(解释与可视化)

切片写入 Chroma 时,每条切片的文本会被转换为**向量(embedding)**再存储,便于后续按语义检索。本项目的两种方式如下。

方式一:Chroma 默认嵌入(当前默认)

不传入预计算向量时,Chroma 使用内置的默认嵌入模型(如 all-MiniLM-L6-v2)在服务端对 documents 做向量化,无需配置 API Key,适合本地/演示。

方式二:LangChain Embeddings(可选)

设置 use_langchain_embeddings=True 且配置 OPENAI_API_KEY 时,使用 OpenAIEmbeddings 在本地先对全部切片调用嵌入 API 得到向量,再与 documentsmetadatas 一起写入 Chroma。适合需要统一嵌入模型或与后续 RAG 链一致时使用。

从切片到向量的数据流:

flowchart LR subgraph input [输入] Chunks[文本切片列表] Meta[元数据列表] end subgraph embed [向量化] E1[Chroma 默认嵌入] E2[或 LangChain OpenAIEmbeddings] end subgraph store [存储] Vec[向量] ChromaCol[course_docs 集合] end Chunks --> E1 Chunks --> E2 E1 --> Vec E2 --> Vec Vec --> ChromaCol Meta --> ChromaCol Chunks --> ChromaCol

检索时的使用方式: 用户问题经同一套嵌入模型得到查询向量,Chroma 按余弦相似度返回最相似的若干切片及其 metadata(如 doc_id、section_title),供 RAG 生成回答与引用。

嵌入维度与模型名:

方式 模型 向量维度
Chroma 默认 all-MiniLM-L6-v2(sentence-transformers) 384
LangChain OpenAIEmbeddings text-embedding-3-small(默认)或配置指定 1536(small)等

单条切片从文本到向量的示例:

4.5 测试结果

见 §6 测试汇总。


5. 知识图谱构建(2.4)

5.1 2.4.1 基础结构图谱

对应 tasks 2.4.1.1~2.4.1.4。

从 doc_index 到 Topic/Exercise 的字段映射表:

目标节点 doc_index 来源 字段映射
Topic doc_type == "lecture" 的条目 id←doc_id,name←doc_id,name_en←title,order←lecture_index,difficulty←"intermediate",description←description
Exercise doc_type == "homework" 的条目 id←doc_id,title←title,difficulty←"intermediate",source←"homework-{编号}",problem_type←"mixed"

build_and_ingest_graph 的调用顺序:

flowchart TB A[doc_index_path 或 results_dir] B[load_doc_index 或 build_doc_index] C[build_topics_and_exercises] D[ingest_to_neo4j] A --> B B --> C C --> D subgraph ingest [ingest_to_neo4j 内部] D1[clear_first 删除本课程节点含 Concept] D2[MERGE Topic 节点] D3[MERGE Exercise 节点] D4[创建 PREREQUISITE 边] D5[创建 COVERS 边] D6[2.4.2 概念与 TEACHES/PRACTICES/DEPENDS_ON] D7[2.4.3 索引] D1 --> D2 D2 --> D3 D3 --> D4 D4 --> D5 D5 --> D6 D6 --> D7 end D --> D1

5.2 2.4.2 概念抽取与细粒度关系

对应 tasks 2.4.2.1~2.4.2.5。

概念层示意(以 lec01 为例)。对应 2.4.2 概念与 TEACHES/PRACTICES/DEPENDS_ON。

图例说明:

元素 含义
节点颜色 浅蓝 = Topic(讲义,如 lec01);浅绿 = Exercise(作业,如 hw01);麦色 = Concept(概念,图中为章节标题的 slug)
蓝色实线箭头 TEACHES:讲义 → 概念,表示该讲讲授该概念
绿色虚线箭头 PRACTICES:作业 → 概念,表示该作业练习该概念
橙色箭头 DEPENDS_ON:概念 → 概念,表示先修顺序(同讲内按章节顺序)
深绿粗虚线箭头 COVERS:讲义 → 作业,表示 lec0i 覆盖 hw0i

示例(以图中概念 markerboard12a 为例):该节点对应概念 id lec01_markerboard12a,来自 lec01 中某节标题的 slug(如 "Marker Board 12a")。若有一条蓝色箭头从 lec01 指向 markerboard12a,表示 lec01 讲授该概念(TEACHES);若有一条绿色虚线箭头从 hw01 指向它,表示 hw01 练习该概念(PRACTICES);若有一条橙色箭头从另一概念指向 markerboard12a,表示学习 markerboard12a 前需先掌握前一概念(DEPENDS_ON)。

概念层示意:lec01 与对应概念、hw01

实现位置:src/knowledge_graph/concepts.pyextract_concepts_from_lectureingest_concepts_and_relations_to_neo4j);build.py 在写入 COVERS 后若提供 doc_indexresults_dir 则调用概念抽取并写入 Neo4j;clear_first 时同时删除 Concept 及 TEACHES/PRACTICES/DEPENDS_ON。

5.3 2.4.3 图谱验证与优化

验证结果示例:

先修链: 通过 []
概念覆盖: 通过 {'by_lecture': {'lec01': 17, ...}, 'missing_teaches': []}

6. 测试汇总

测试汇总结果:

============================= test session starts =============================
platform win32 -- Python 3.13.5, pytest-8.4.2, pluggy-1.5.0 -- C:\Users\10489\miniconda3\python.exe
cachedir: .pytest_cache
rootdir: D:\Workspace\agent-edu
plugins: anyio-4.11.0, langsmith-0.4.34
collecting ... collected 11 items / 1 deselected / 10 selected

tests/test_doc_index.py::test_parse_md_filename PASSED                   [ 10%]
tests/test_doc_index.py::test_hash_to_uuid PASSED                        [ 20%]
tests/test_doc_index.py::test_build_doc_index_and_relations PASSED       [ 30%]
tests/test_doc_index.py::test_analyze_md_structure PASSED                [ 40%]
tests/test_doc_index.py::test_save_and_load_doc_index PASSED             [ 50%]
tests/test_md_loader.py::test_clean_md_text PASSED                       [ 60%]
tests/test_md_loader.py::test_parse_md_headings PASSED                   [ 70%]
tests/test_md_loader.py::test_load_md PASSED                             [ 80%]
tests/test_splitter.py::test_slice_document_and_metadata PASSED          [ 90%]
tests/test_knowledge_graph.py::test_build_topics_and_exercises PASSED    [100%]

====================== 10 passed, 1 deselected in 0.06s =======================

7. 模块与文件对应

任务项 模块/文件 说明
2.2 文档索引 src/preprocessing/doc_index.py 扫描、解析、content_map/data.json 补全、保存 config/doc_index.json
2.1.2 / 2.1.3 src/preprocessing/md_loader.py clean_md_text、parse_md_headings、load_md
2.3.1 / 2.3.2 src/preprocessing/splitter.py 按章节与长度切片、元数据、chunk_metadata_for_chroma
2.3.3 Chroma src/preprocessing/chroma_ingest.py 索引 → 切片 → 写入 Chroma 集合 course_docs
2.4.1 基础图谱 src/knowledge_graph/build.py Topic/Exercise、PREREQUISITE、COVERS、Neo4j 导入
2.4.2 概念与关系 src/knowledge_graph/concepts.py Concept 抽取、TEACHES/PRACTICES/DEPENDS_ON
2.4.3 验证与索引 src/knowledge_graph/validate.pyscripts/validate_knowledge_graph.py 先修/覆盖验证、索引创建

8. 与 tasks.md 的对应

tasks.md 子项 实现方式
2.1.1 PDF→文本 本阶段跳过,直接使用 results/*.md
2.1.2 文本清洗和格式化 md_loader.clean_md_text
2.1.3 章节结构识别和标注 md_loader.parse_md_headings、Section 结构
2.2.1 分析现有文档 doc_index.analyze_md_structure、结构报告
2.2.2 建立文档索引 doc_index.build_doc_index、config/doc_index.json
2.2.3 分类和关联 文件名解析 doc_type/doc_id、related_* 字段
2.2.4 前 5 讲优先 15 个文件全部纳入(5 讲 + 5 作业 + 5 答案)
2.3.1 智能切片 splitter 按章节 + 长度与重叠、避免公式切断
2.3.2 元数据标注 ChunkWithMeta、chunk_metadata_for_chroma
2.3.3 Chroma 存储 chroma_ingest.ingest_results_to_chroma、集合 course_docs
2.4.1.1~2.4.1.4 基础结构图谱 build_topics_and_exercises、ingest_to_neo4j、Topic/Exercise/PREREQUISITE/COVERS
2.4.2.1~2.4.2.5 概念与细粒度关系 concepts.extract_concepts_from_lecture、ingest_concepts_and_relations_to_neo4j、Concept/TEACHES/PRACTICES/DEPENDS_ON
2.4.3.1 先修关系合理性 validate.validate_prerequisite_chain、scripts/validate_knowledge_graph.py
2.4.3.2 概念覆盖完整性 validate.validate_concept_coverage
2.4.3.3 查询性能优化 validate.ensure_indexes、Topic/Concept/Exercise.id 索引

9. Task3 实现摘要(3.2 / 3.3)

9.1 知识图谱完善与可视化(3.2)

9.2 数据质量优化(3.3)